/*******************************************************************************
 * Copyright (c) 2001, 2002 Boris Pruessmann and others. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Common Public License v0.5 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/cpl-v05.html
 *
 * Contributors: Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.ui.views;

import java.text.DateFormat;
import java.text.MessageFormat;

import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.api.ILogEntry;
import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.api.IPerforceServerManager;
import net.sourceforge.perforce.core.resources.LocalResource;
import net.sourceforge.perforce.core.resources.RemoteFile;
import net.sourceforge.perforce.core.syncinfo.ResourceSyncInfo;
import net.sourceforge.perforce.ui.PerforceUIPlugin;
import net.sourceforge.perforce.ui.internal.Policy;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.viewers.ColumnLayoutData;
import org.eclipse.jface.viewers.ColumnPixelData;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableLayout;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.team.core.TeamException;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.ViewPart;

/**
 * @version $Revision: 489 $
 * @author <a href="mailto:bpruessmann@users.sourceforge.net">Boris Pr??mann</a>
 */
public class HistoryView extends ViewPart {
  //----------------------------------------------------------------------------
  // Public Constants

  /** The view's identifier */
  public static final String ID = PerforceUIPlugin.ID + ".views.HistoryView"; //$NON-NLS-1$

  // ---------------------------------------------------------------------------
  // Private Constants

  /** Column Index: Revision */
  private static final int COL_REVISION = 0;
  /** Column Index: Filename */
  private static final int COL_FILENAME = 1;
  /** Column Index: Changelist */
  private static final int COL_CHANGELIST = 2;
  /** Column Index: Date */
  private static final int COL_DATE = 3;
  /** Column Index: User */
  private static final int COL_USER = 4;
  /** Column Index: Action */
  private static final int COL_ACTION = 5;
  /** Column Index: Description */
  private static final int COL_DESCRIPTION = 6;

  /** Memento Tag: Follow Branches */
  private static final String TAG_FOLLOW_BRANCHES = "follow-branches"; //$NON-NLS-1$
  /** Memento Tag: Column Index */
  private static final String TAG_COLUMN = "column"; //$NON-NLS-1$
  /** Memento Tag: Number */
  private static final String TAG_NUMBER = "number"; //$NON-NLS-1$
  /** Memento Tag: Follow Widht */
  private static final String TAG_WIDTH = "width"; //$NON-NLS-1$

  /** Array of column headings */
  private static final String[] COLUMN_HEADINGS = {Policy.bind("HistoryView.revision"), //$NON-NLS-1$
      Policy.bind("HistoryView.filename"), //$NON-NLS-1$
      Policy.bind("HistoryView.changelist"), //$NON-NLS-1$
      Policy.bind("HistoryView.date"), //$NON-NLS-1$
      Policy.bind("HistoryView.user"), //$NON-NLS-1$
      Policy.bind("HistoryView.action"), //$NON-NLS-1$
      Policy.bind("HistoryView.description") //$NON-NLS-1$
      };

  // ---------------------------------------------------------------------------
  // Private Variables

  /** Array of Column Layouts */
  private ColumnLayoutData[] columnLayouts =
      {new ColumnWeightData(10, true), new ColumnWeightData(10, true),
          new ColumnWeightData(10, true), new ColumnWeightData(10, true),
          new ColumnWeightData(10, true), new ColumnWeightData(10, true),
          new ColumnWeightData(40, true)};

  /** Log entries to display. */
  private ILogEntry[] entries;

  /** Stores the Outer SashForm */
  private SashForm sashForm;
  /** Stores the Inner SashForm */
  private SashForm innerSashForm;
  /** Stores the Revisions Viewer */
  private TableViewer revisionsViewer;
  /** Stores the Action Viewer */
  private TextViewer actionViewer;
  /** Stores the Description Viewer */
  private TextViewer descriptionViewer;

  /** Stores the "Follow Branches" action */
  private Action followBranchesAction;
  /** Stores the memento */
  private IMemento memento;

  // -----------------------------------------------------------------------
  // Public Static Methods

  /**
   * Makes the history view visible in the active perspective. If there isn't a
   * history view registered <code>null</code> is returned. Otherwise the opened
   * view part is returned.
   *
   * @return the HistoryView
   */
  public static HistoryView openInActivePerspective() {
    try {
      return (HistoryView) PerforceUIPlugin.getActivePage().showView(HistoryView.ID);
    } catch (PartInitException pe) {
      return null;
    }
  }

  //----------------------------------------------------------------------------
  // -- Public Methods

  @Override
  public void init(IViewSite site, IMemento memento) throws PartInitException {
    super.init(site, memento);
    this.memento = memento;
  }

  @Override
  public void createPartControl(Composite parent) {
    sashForm = new SashForm(parent, SWT.VERTICAL);
    sashForm.setLayoutData(new GridData(GridData.FILL_BOTH));

    revisionsViewer = createRevisionsViewer(sashForm);
    innerSashForm = new SashForm(sashForm, SWT.HORIZONTAL);

    actionViewer = createActionViewer(innerSashForm);
    descriptionViewer = createDescriptionViewer(innerSashForm);

    sashForm.setWeights(new int[] {60, 40});
    innerSashForm.setWeights(new int[] {50, 50});

    contributeActions();
    memento = null;
  }

  @Override
  public void setFocus() {
    sashForm.setFocus();
  }

  /**
   * Shows the history for the given IP4RemoteFile
   *
   * @param file the remote file.
   */
  public void showHistory(RemoteFile file) {
    if (file == null) {
      revisionsViewer.setInput(null);
      setTitle(Policy.bind("HistoryView.title")); //$NON-NLS-1$
      return;
    }

    revisionsViewer.setInput(file);
    setTitle(Policy.bind("HistoryView.titleWithArgument", file.getDepotFilename())); //$NON-NLS-1$
  }

  /**
   * Shows the history for the given resource.
   *
   * @param resource the resource.
   */
  public void showHistory(IResource resource) {
    if (resource instanceof IFile) {
      try {
        IFile file = (IFile) resource;

        ResourceSyncInfo syncInfo = LocalResource.createFromResource(file).getSyncInfo();
        IPerforceServerManager manager = PerforceCorePlugin.getServerManager();
        IPerforceServer server = manager.getPerforceServer(file);

        RemoteFile remoteFile = new RemoteFile(server, syncInfo.getDepotFileName(), "#head");

        revisionsViewer.setInput(remoteFile);
        setTitle(Policy.bind("HistoryView.titleWithArgument", //$NON-NLS-1$
            remoteFile.getDepotFilename()));
      } catch (TeamException e) {
        PerforceUIPlugin.log(e.getStatus());
      }

      return;
    }

    revisionsViewer.setInput(null);
    setTitle(Policy.bind("HistoryView.title")); //$NON-NLS-1$
  }

  @Override
  public void saveState(IMemento memento) {
    String bool = followBranchesAction.isChecked() ? "false" : "true"; //$NON-NLS-1$ //$NON-NLS-2$
    memento.putString(TAG_FOLLOW_BRANCHES, bool);

    // save columns width
    Table table = revisionsViewer.getTable();
    TableColumn columns[] = table.getColumns();

    // check whether it has ever been layed out
    boolean shouldSave = false;
    for (int i = 0; i < columns.length; i++) {
      if (columnLayouts[i].resizable && columns[i].getWidth() != 0) {
        shouldSave = true;
        break;
      }
    }

    if (shouldSave) {
      for (int i = 0; i < columns.length; i++) {
        if (columnLayouts[i].resizable) {
          IMemento child = memento.createChild(TAG_COLUMN);
          child.putInteger(TAG_NUMBER, i);
          child.putInteger(TAG_WIDTH, columns[i].getWidth());
        }
      }
    }
  }

  //----------------------------------------------------------------------------
  // - Private Methods

  /** Called to contribute the view's actions. */
  private void contributeActions() {
    createFollowBranchesAction();

    contributeToolbarActions();
    contributeMenuActions();
  }

  /** Called to contribute the view's menu actions. */
  private void contributeMenuActions() {
    // Contribute actions to popup menu
    MenuManager menuMgr = new MenuManager();
    Menu menu = menuMgr.createContextMenu(revisionsViewer.getTable());
    menuMgr.addMenuListener(new IMenuListener() {
      public void menuAboutToShow(IMenuManager menuMgr) {
        menuMgr.add(new Separator(IWorkbenchActionConstants.GROUP_FILE));
      }
    });
    menuMgr.setRemoveAllWhenShown(true);
    revisionsViewer.getTable().setMenu(menu);
    getSite().registerContextMenu(menuMgr, revisionsViewer);
  }

  /** Contributes actions to the view's toolbar */
  private void contributeToolbarActions() {
    IActionBars bars = getViewSite().getActionBars();
    bars.getMenuManager().add(followBranchesAction);
  }

  /** Creates the follow branches action. */
  private void createFollowBranchesAction() {
    boolean state = false;
    if (memento != null) {
      state = Boolean.valueOf((memento.getString(TAG_FOLLOW_BRANCHES))).booleanValue();
    }

    followBranchesAction = new Action(Policy.bind("HistoryView.followBranches")) //$NON-NLS-1$
        {
          @Override
          public void run() {
            entries = null;
            revisionsViewer.refresh();
          }
        };
    followBranchesAction.setChecked(state);
  }

  /**
   * Create the description viewer.
   *
   * @param parent the parent composite.
   * @return the TextViewer
   */
  private TextViewer createDescriptionViewer(Composite parent) {
    TextViewer result =
        new TextViewer(parent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.BORDER | SWT.READ_ONLY);
    return result;
  }

  /**
   * Creates the action viewer.
   *
   * @param parent the parent composite
   * @return TextViewer the text viewer.
   */
  private TextViewer createActionViewer(Composite parent) {
    TextViewer result =
        new TextViewer(parent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.BORDER | SWT.READ_ONLY);
    return result;
  }

  /**
   * Creates the revisions viewer.
   *
   * @param parent the parent composite.
   * @return TableViewer the table viewer.
   */
  private TableViewer createRevisionsViewer(Composite parent) {
    Table table = new Table(parent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION);
    table.setHeaderVisible(true);
    table.setLinesVisible(true);
    table.setLayoutData(new GridData(GridData.FILL_BOTH));

    TableLayout layout = new TableLayout();
    table.setLayout(layout);

    createColumns(table, layout);

    TableViewer result = new TableViewer(table);
    result.setContentProvider(new IStructuredContentProvider() {
      public Object[] getElements(Object inputElement) {
        if (entries != null) {
          return entries;
        }

        if (!(inputElement instanceof RemoteFile)) {
          return null;
        }

        final RemoteFile remoteFile = (RemoteFile) inputElement;
        final Object[][] result = new Object[1][];
        final TeamException[] ex = new TeamException[1];
        BusyIndicator.showWhile(getViewSite().getShell().getDisplay(), new Runnable() {
          public void run() {
            try {
              boolean follow = followBranchesAction.isChecked();
              entries = remoteFile.getLogEntries(follow, new NullProgressMonitor());
              result[0] = entries;
            } catch (TeamException e) {
              ex[0] = e;
            }
          }
        });

        if (ex[0] != null) {
          ErrorDialog.openError(getViewSite().getShell(), null, null, ex[0].getStatus());
          return new Object[0];
        }

        return result[0];
      }

      public void dispose() {
      }

      public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        entries = null;
      }
    });
    result.setLabelProvider(new HistoryLabelProvider());
    result.addSelectionChangedListener(new ISelectionChangedListener() {
      public void selectionChanged(SelectionChangedEvent event) {
        ISelection selection = event.getSelection();
        if (selection == null || !(selection instanceof IStructuredSelection)) {
          descriptionViewer.setDocument(new Document("")); //$NON-NLS-1$
          actionViewer.setDocument(new Document("")); //$NON-NLS-1$
          return;
        }

        IStructuredSelection ss = (IStructuredSelection) selection;
        if (ss.size() != 1) {
          descriptionViewer.setDocument(new Document("")); //$NON-NLS-1$
          actionViewer.setDocument(new Document("")); //$NON-NLS-1$
          return;
        }

        ILogEntry entry = (ILogEntry) ss.getFirstElement();
        descriptionViewer.setDocument(new Document(entry.getDescription()));

        String[] actions = entry.getActions();
        StringBuilder buffer = new StringBuilder();
        for (int i = 0, length = actions.length; i < length; i++) {
          buffer.append(actions[i]).append('\n');
        }

        actionViewer.setDocument(new Document(buffer.toString()));
      }
    });

    return result;
  }

  /**
   * Create the table's columns.
   *
   * @param table the table
   * @param layout the table's layout.
   */
  private void createColumns(Table table, TableLayout layout) {
    if (memento != null) {
      IMemento children[] = memento.getChildren(TAG_COLUMN);
      if (children != null) {
        for (int i = 0; i < children.length; i++) {
          Integer val = children[i].getInteger(TAG_NUMBER);
          if (val != null) {
            int index = val.intValue();
            val = children[i].getInteger(TAG_WIDTH);
            if (val != null && val.intValue() != -1) {
              columnLayouts[index] = new ColumnPixelData(val.intValue(), true);
            }
          }
        }
      }
    }

    for (int i = 0, lentgh = COLUMN_HEADINGS.length; i < lentgh; i++) {
      TableColumn column = new TableColumn(table, SWT.NONE);
      column.setResizable(true);
      column.setText(COLUMN_HEADINGS[i]);
      layout.addColumnData(columnLayouts[i]);
    }
  }

  // -----------------------------------------------------------------------
  // Private Inner Classes

  /** Label Provider for the HistoryView */
  private class HistoryLabelProvider extends LabelProvider implements ITableLabelProvider {
    /** @see ITableLabelProvider#getColumnImage(Object, int) */
    public Image getColumnImage(Object element, int columnIndex) {
      return null;
    }

    /** @see ITableLabelProvider#getColumnText(Object, int) */
    public String getColumnText(Object element, int columnIndex) {
      ILogEntry entry = (ILogEntry) element;
      switch (columnIndex) {
        case COL_REVISION:
          return entry.getRevision().toString();

        case COL_FILENAME:
          return entry.getDepotFilename();

        case COL_CHANGELIST:
          return MessageFormat.format("{0,number,00000}", //$NON-NLS-1$
              new Object[] {entry.getChangelist()});

        case COL_DATE:
          return DateFormat.getInstance().format(entry.getTime());

        case COL_USER:
          return entry.getUser() + '@' + entry.getClient();

        case COL_ACTION:
          String[] actions = entry.getActions();
          StringBuilder buffer = new StringBuilder();
          for (int i = 0, length = actions.length; i < length; i++) {
            buffer.append(actions[i]);
            if (i < length - 1) {
              buffer.append("; "); //$NON-NLS-1$
            }
          }
          return buffer.toString();

        case COL_DESCRIPTION:
          String description = entry.getDescription().trim();
          int index = description.indexOf('\n');
          switch (index) {
            case 0:
            case -1:
              return description;

            default:
              return description.substring(0, index) + "[...]"; //$NON-NLS-1$
          }
      }

      return ""; //$NON-NLS-1$
    }
  }
}
